/*
 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.corba.se.impl.interceptors;

import org.omg.CORBA.Any;
import org.omg.CORBA.ORB;
import org.omg.CORBA.TypeCode;
import org.omg.CORBA.LocalObject;

import com.sun.corba.se.spi.ior.iiop.GIOPVersion;
import com.sun.corba.se.spi.logging.CORBALogDomains;

import sun.corba.EncapsInputStreamFactory;

import com.sun.corba.se.impl.corba.AnyImpl;
import com.sun.corba.se.impl.encoding.EncapsInputStream;
import com.sun.corba.se.impl.encoding.EncapsOutputStream;
import com.sun.corba.se.impl.logging.ORBUtilSystemException;

import org.omg.IOP.Codec;
import org.omg.IOP.CodecPackage.FormatMismatch;
import org.omg.IOP.CodecPackage.InvalidTypeForEncoding;
import org.omg.IOP.CodecPackage.TypeMismatch;

/**
 * CDREncapsCodec is an implementation of Codec, as described
 * in orbos/99-12-02, that supports CDR encapsulation version 1.0, 1.1, and
 * 1.2.
 */
public final class CDREncapsCodec
    extends org.omg.CORBA.LocalObject
    implements Codec {

  // The ORB that created the factory this codec was created from
  private ORB orb;
  ORBUtilSystemException wrapper;

  // The GIOP version we are encoding for
  private GIOPVersion giopVersion;

    /*
     *******************************************************************
     * NOTE: CDREncapsCodec must remain immutable!  This is so that we
     * can pre-create CDREncapsCodecs for each version of GIOP in
     * CodecFactoryImpl.
     *******************************************************************/

  /**
   * Creates a new codec implementation.  Uses the given ORB to create
   * CDRInputStreams when necessary.
   *
   * @param orb The ORB to use to create a CDRInputStream or CDROutputStream
   * @param major The major version of GIOP we are encoding for
   * @param minor The minor version of GIOP we are encoding for
   */
  public CDREncapsCodec(ORB orb, int major, int minor) {
    this.orb = orb;
    wrapper = ORBUtilSystemException.get(
        (com.sun.corba.se.spi.orb.ORB) orb, CORBALogDomains.RPC_PROTOCOL);

    giopVersion = GIOPVersion.getInstance((byte) major, (byte) minor);
  }

  /**
   * Convert the given any into a CDR encapsulated octet sequence
   */
  public byte[] encode(Any data)
      throws InvalidTypeForEncoding {
    if (data == null) {
      throw wrapper.nullParam();
    }
    return encodeImpl(data, true);
  }

  /**
   * Decode the given octet sequence into an any based on a CDR
   * encapsulated octet sequence.
   */
  public Any decode(byte[] data)
      throws FormatMismatch {
    if (data == null) {
      throw wrapper.nullParam();
    }
    return decodeImpl(data, null);
  }

  /**
   * Convert the given any into a CDR encapsulated octet sequence.  Only
   * the data is stored.  The type code is not.
   */
  public byte[] encode_value(Any data)
      throws InvalidTypeForEncoding {
    if (data == null) {
      throw wrapper.nullParam();
    }
    return encodeImpl(data, false);
  }

  /**
   * Decode the given octet sequence into an any based on a CDR
   * encapsulated octet sequence.  The type code is expected not to appear
   * in the octet sequence, and the given type code is used instead.
   */
  public Any decode_value(byte[] data, TypeCode tc)
      throws FormatMismatch, TypeMismatch {
    if (data == null) {
      throw wrapper.nullParam();
    }
    if (tc == null) {
      throw wrapper.nullParam();
    }
    return decodeImpl(data, tc);
  }

  /**
   * Convert the given any into a CDR encapsulated octet sequence.
   * If sendTypeCode is true, the type code is sent with the message, as in
   * a standard encapsulation.  If it is false, only the data is sent.
   * Either way, the endian type is sent as the first part of the message.
   */
  private byte[] encodeImpl(Any data, boolean sendTypeCode)
      throws InvalidTypeForEncoding {
    if (data == null) {
      throw wrapper.nullParam();
    }

    // _REVISIT_ Note that InvalidTypeForEncoding is never thrown in
    // the body of this method.  This is due to the fact that CDR*Stream
    // will never throw an exception if the encoding is invalid.  To
    // fix this, the CDROutputStream must know the version of GIOP it
    // is encoding for and it must check to ensure that, for example,
    // wstring cannot be encoded in GIOP 1.0.
    //
    // As part of the GIOP 1.2 work, the CDRInput and OutputStream will
    // be versioned.  This can be handled once this work is complete.

    // Create output stream with default endianness.
    EncapsOutputStream cdrOut =
        sun.corba.OutputStreamFactory.newEncapsOutputStream(
            (com.sun.corba.se.spi.orb.ORB) orb, giopVersion);

    // This is an encapsulation, so put out the endian:
    cdrOut.putEndian();

    // Sometimes encode type code:
    if (sendTypeCode) {
      cdrOut.write_TypeCode(data.type());
    }

    // Encode value and return.
    data.write_value(cdrOut);

    return cdrOut.toByteArray();
  }

  /**
   * Decode the given octet sequence into an any based on a CDR
   * encapsulated octet sequence.  If the type code is null, it is
   * expected to appear in the octet sequence.  Otherwise, the given
   * type code is used.
   */
  private Any decodeImpl(byte[] data, TypeCode tc)
      throws FormatMismatch {
    if (data == null) {
      throw wrapper.nullParam();
    }

    AnyImpl any = null;  // return value

    // _REVISIT_ Currently there is no way for us to distinguish between
    // a FormatMismatch and a TypeMismatch because we cannot get this
    // information from the CDRInputStream.  If a RuntimeException occurs,
    // it is turned into a FormatMismatch exception.

    try {
      EncapsInputStream cdrIn = EncapsInputStreamFactory.newEncapsInputStream(orb, data,
          data.length, giopVersion);

      cdrIn.consumeEndian();

      // If type code not specified, read it from octet stream:
      if (tc == null) {
        tc = cdrIn.read_TypeCode();
      }

      // Create a new Any object:
      any = new AnyImpl((com.sun.corba.se.spi.orb.ORB) orb);
      any.read_value(cdrIn, tc);
    } catch (RuntimeException e) {
      // See above note.
      throw new FormatMismatch();
    }

    return any;
  }
}
